/* Copyright 2013 (C) FatalEror / Dean Bahari
 * 
 * jroCK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * See LICENSE file in main folder for more information.
 */

package jrock.filesystem;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.ARBVertexBufferObject.*;

import jrock.other.Utility;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.lwjgl.BufferUtils;

/**
 * @author FatalEror
 */
public class GND {
    
    private final String magicKey   = "GRGN";
    private int          version    = 0;
    private int          width      = 0;
    private int          height     = 0;
    private float        scale      = 0;
    private BMP[]        texture    = null;
    private Surface[]    surface    = null;
    private Tile[]       tile       = null;
    private int[]        tilePerTex = null;
    private IntBuffer    vboIndex   = null;
    private Lightmap[]   lmap       = null;
    
    private class Lightmap {
        byte[] grid  = null;
        byte[] color = null;
    }
    
    private class Surface {
        private float[] u      = new float[4];
        private float[] v      = new float[4];
        private short   texID  = 0;
        private short   lmapID = 0;
        private byte[]  color  = new byte[4];
    }
    
    private class Tile {
        private float[] y         = new float[4];
        private int[]   surfaceID = new int[3];
    }
    
    private void push() {
        vboIndex = BufferUtils.createIntBuffer(texture.length);
        glGenBuffersARB(vboIndex);
        for (int i = 0; i < texture.length; i++) {
            FloatBuffer fb = BufferUtils.createFloatBuffer(tilePerTex[i] * 30);
            for (int z = 0; z < height; z++) {
                for (int x = 0; x < width; x++) {
                    /*        Tile layout
                     * 
                     * y0--------y1 y0--------y1
                     * | (0, 0)   | | (1, 0)   |
                     * | Center   | | Next     |
                     * |    Tile  | |    Tile  |
                     * |          | |          |
                     * y2--------y3 y2--------y3
                     * y0--------y1                      u1--------u3
                     * | (1, 0)   |                      |  This is |
                     * | Front    |                      | For      |
                     * |    Tile  |                      |  Texture |
                     * |          |                      |      UV  |
                     * y2--------y3                      u0--------u2
                     * 
                     */

                    Tile    center = tile[z * width + x];
                    Surface current;
                    
                    // Center tile
                    if (center.surfaceID[0] >= 0 && (current = surface[center.surfaceID[0]]).texID == i) {
                        fb.put(x       ).put(center.y[0] / scale).put(z       );
                            fb.put(current.u[0]).put(current.v[0]);
                        fb.put(x + 1.0f).put(center.y[3] / scale).put(z + 1.0f);
                            fb.put(current.u[3]).put(current.v[3]);
                        fb.put(x + 1.0f).put(center.y[1] / scale).put(z       );
                            fb.put(current.u[1]).put(current.v[1]);
                        fb.put(x       ).put(center.y[0] / scale).put(z       );
                            fb.put(current.u[0]).put(current.v[0]);
                        fb.put(x       ).put(center.y[2] / scale).put(z + 1.0f);
                            fb.put(current.u[2]).put(current.v[2]);
                        fb.put(x + 1.0f).put(center.y[3] / scale).put(z + 1.0f);
                            fb.put(current.u[3]).put(current.v[3]);
                    }
                    // Front tile
                    if (center.surfaceID[1] >= 0 && (current = surface[center.surfaceID[1]]).texID == i) {
                        Tile front = tile[(z + 1) * width + x];
                        fb.put(x       ).put(center.y[2] / scale).put(z + 1.0f);
                            fb.put(current.u[0]).put(current.v[0]);
                        fb.put(x + 1.0f).put( front.y[1] / scale).put(z + 1.0f);
                            fb.put(current.u[3]).put(current.v[3]);
                        fb.put(x + 1.0f).put(center.y[3] / scale).put(z + 1.0f);
                            fb.put(current.u[1]).put(current.v[1]);
                        fb.put(x       ).put(center.y[2] / scale).put(z + 1.0f);
                            fb.put(current.u[0]).put(current.v[0]);
                        fb.put(x       ).put( front.y[0] / scale).put(z + 1.0f);
                            fb.put(current.u[2]).put(current.v[2]);
                        fb.put(x + 1.0f).put( front.y[1] / scale).put(z + 1.0f);
                            fb.put(current.u[3]).put(current.v[3]);
                    }
                    // Next tile
                    if (center.surfaceID[2] >= 0 && (current = surface[center.surfaceID[2]]).texID == i) {
                        Tile next = tile[z * width + x + 1];
                        //System.out.println("(" + current.u[0] + "," + current.v[0] + ")" + " (" + current.u[1] + "," + current.v[1] + ")" + " (" + current.u[2] + "," + current.v[2] + ")" + " (" + current.u[3] + "," + current.v[3] + ")");
                        fb.put(x + 1.0f).put(center.y[1] / scale).put(z       );
                            fb.put(current.u[1]).put(current.v[1]);
                        fb.put(x + 1.0f).put(  next.y[2] / scale).put(z + 1.0f);
                            fb.put(current.u[2]).put(current.v[2]);
                        fb.put(x + 1.0f).put(  next.y[0] / scale).put(z       );
                            fb.put(current.u[3]).put(current.v[3]);
                        fb.put(x + 1.0f).put(center.y[1] / scale).put(z       );
                            fb.put(current.u[1]).put(current.v[1]);
                        fb.put(x + 1.0f).put(center.y[3] / scale).put(z + 1.0f);
                            fb.put(current.u[0]).put(current.v[0]);
                        fb.put(x + 1.0f).put(  next.y[2] / scale).put(z + 1.0f);
                            fb.put(current.u[2]).put(current.v[2]);
                    }
                }
            }
            fb.flip();
            
            glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboIndex.get(i));
            glBufferDataARB(GL_ARRAY_BUFFER_ARB, fb, GL_STATIC_DRAW_ARB);
        }
        
        // Calculating lightmaps and push it into vbo
        /*for (int z = 0; z < height; z++) {
            for (int x = 0; x < width; x++) {
                surfaceItem surface = surface[tile[z * width + x].surfaceID[0]];
                if (surface.lmapID >= 0) {
                    
                }
            }
        }*/
    }
    
    public void draw() {
        glPushMatrix();
        glTranslatef(-width * scale / 2.0f, 0.0f, -height * scale / 2.0f);
        glScalef(scale, scale, scale);
        for (int i = 0; i < texture.length; i++) {
            texture[i].bind();
            glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboIndex.get(i));
            glVertexPointer(3, GL_FLOAT, 20, 0);
            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
            glTexCoordPointer(2, GL_FLOAT, 20, 12);
            glDrawArrays(GL_TRIANGLES, 0, tilePerTex[i] * 6);
        }
        glPopMatrix();
    }
    
    public boolean read(GRF grf, String fileName) {
        try {
            ByteBuffer bb = grf.getData("data\\" + fileName);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            
            // Check if input file is a valid GND file by its signature/magic key
            byte[] tmpData = new byte[magicKey.length()];
            bb.get(tmpData, 0, magicKey.length());
            if (!magicKey.contentEquals(new String(tmpData))) {
                throw new Exception("Error: " + fileName + " is not a valid GND file.");
            }
            
            version = bb.get() * 10 + bb.get();
            width   = bb.getInt();
            height  = bb.getInt();
            scale   = bb.getFloat();
            
            // Textures
            texture     = new BMP[bb.getInt()];
            int lenText = bb.getInt();
            for (int i = 0; i < texture.length; i++) {
                texture[i] = grf.getBMP("texture\\" + Utility.read(bb, 0, lenText));
            }

            // Lightmaps
            lmap           = new Lightmap[bb.getInt()];
            int lmapWidth  = bb.getInt();
            int lmapHeight = bb.getInt();
            int lmapPf     = bb.getInt(); // Usually 1
            
            for (int i = 0; i < lmap.length; i++) {
                lmap[i] = new Lightmap();
                lmap[i].grid  = new byte[lmapWidth * lmapHeight];
                lmap[i].color = new byte[lmapWidth * lmapHeight * 3];
                bb.get(lmap[i].grid, 0, lmapWidth * lmapHeight);
                bb.get(lmap[i].color, 0, lmapWidth * lmapHeight * 3);
            }
            
            // Surfaces
            surface = new Surface[bb.getInt()];
            for (int i = 0; i < surface.length; i++) {
                surface[i] = new Surface();
                surface[i].u[0] = bb.getFloat(); surface[i].u[1] = bb.getFloat();
                surface[i].u[2] = bb.getFloat(); surface[i].u[3] = bb.getFloat();
                surface[i].v[0] = bb.getFloat(); surface[i].v[1] = bb.getFloat();
                surface[i].v[2] = bb.getFloat(); surface[i].v[3] = bb.getFloat();
                surface[i].texID  = bb.getShort();
                surface[i].lmapID = bb.getShort();
                surface[i].color[0] = bb.get(); surface[i].color[1] = bb.get();
                surface[i].color[2] = bb.get(); surface[i].color[3] = bb.get();
            }
            
            // Tiles
            tile       = new Tile[width * height];
            tilePerTex = new int[texture.length];
            for (int i = 0; i < tile.length; i++) {
                tile[i] = new Tile();
                tile[i].y[0] = bb.getFloat(); tile[i].y[1] = bb.getFloat();
                tile[i].y[2] = bb.getFloat(); tile[i].y[3] = bb.getFloat();
                for (int j = 0; j < 3; j++) {
                    tile[i].surfaceID[j] = bb.getInt();
                    if (tile[i].surfaceID[j] >= 0) {
                        tilePerTex[surface[tile[i].surfaceID[j]].texID]++;
                    }
                }
            }
            
        } catch (Exception ex) {
            Logger.getLogger(GND.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        
        push(); // Push all data into VBO
        return true;
    }
    
    public int getWidth() {
        return width;
    }
    
    public int getHeight() {
        return height;
    }
    
    public float getScale() {
        return scale;
    }
    
    public void destroy() {
        glDeleteBuffersARB(vboIndex);
        for (int i = 0; i < texture.length; i++) {
            texture[i].destroy();
        }
    }
}
